Intro

The purpose of this analysis is to find synergy biomarkers using the emba R package in the cascade boolean model datasets (the cascade is the name of the topology used). More specifically, we will investigate the two synergies: AK-PD, PD-PI and try to find causal mechanisms that might explain why and where (pathways) these synergies manifest.

Note that AK is an AKT inhibitor and PI is a PI3K inhibitor and they both target the PI3K/AKT/mTOR pathway. The combination of such an inhibitor with a MEK inhibitor - PD - that targets the MAPK/ERK pathway, has proven to be more effective than the single drug treatment during clinical trials with patients that had advanced colorectal carcinoma (source).

The boolean model datasets are in total \(9\): one for each cell line of interest (8 cell lines) where the models were fitted to a specific steady state in each case and one for the so-called random models which were generated randomly in the sense that were fitted only to a proliferation state (simulations were done using the DrugLogics software modules Gitsbe and Drabme).

Input

First we load the cell-specific input data:

# Cell Lines
cell.lines = c("A498", "AGS", "DU145", "colo205", "SW620", "SF295", "UACC62", "MDA-MB-468")

cell.line.dirs = sapply(cell.lines, function(cell.line) {
  paste0(getwd(), "/", cell.line)
})

# Model predictions
model.predictions.files = sapply(cell.line.dirs, function(cell.line.dir) {
  paste0(cell.line.dir, "/model_predictions")
})

model.predictions.per.cell.line = lapply(model.predictions.files, function(file) {   
  get_model_predictions(file) 
})

# Observed synergies
observed.synergies.files = sapply(cell.line.dirs, function(cell.line.dir) {
  paste0(cell.line.dir, "/observed_synergies")
})

observed.synergies.per.cell.line = lapply(observed.synergies.files, function(file) {
  get_observed_synergies(file)
})

# Models Stable State (1 per model)
models.stable.state.files = sapply(cell.line.dirs, function(cell.line.dir) {
  paste0(cell.line.dir, "/models_stable_state")
})

models.stable.state.per.cell.line = lapply(models.stable.state.files, function(file) { 
  as.matrix(read.table(file, check.names = FALSE))
})

# Models Link Operators
models.link.operator.files = sapply(cell.line.dirs, function(cell.line.dir) {
  paste0(cell.line.dir, "/models_link_operator")
})

models.link.operators.per.cell.line = lapply(models.link.operator.files, function(file) {
  as.matrix(read.table(file, check.names = FALSE))
})

The random model input data:

random.dir = paste0(getwd(), "/random")
random.model.predictions = get_model_predictions(paste0(random.dir, "/model_predictions"))

random.models.stable.state = as.matrix(
  read.table(file = paste0(random.dir, "/models_stable_state"), check.names = FALSE)
)

random.models.link.operator =
  as.matrix(read.table(file = paste0(random.dir, "/models_link_operator"), check.names = FALSE))

# the node names used in our analysis
node.names = colnames(random.models.stable.state)
# the tested drug combinations
drug.combos = colnames(random.model.predictions)

Synergy Biomarker Analysis

Using the generic function biomarker_synergy_analysis from the emba R package, we can find synergy biomarkers i.e. nodes whose activity and boolean equation parameterization (link operator) affect the manifestation of synergies in the boolean models. Models are classified based on whether they predict or not each of the predicted synergies found in each boolean dataset.

First we run the analysis on the cell-specific boolean model datasets (note that every input to the biomarker_synergy_analysis function changes per cell line):

cell.specific.synergy.analysis.res = list()

for (cell.line in cell.lines) {
  cell.specific.synergy.analysis.res[[cell.line]] =
    biomarker_synergy_analysis(model.predictions.per.cell.line[[cell.line]],
      models.stable.state.per.cell.line[[cell.line]], 
      models.link.operators.per.cell.line[[cell.line]],
      observed.synergies.per.cell.line[[cell.line]], threshold = 0.7)
}

Next we run the analysis on the random boolean model datasets (note that the only input to the biomarker_synergy_analysis function that changes is the observed synergies per cell line - the rest is the same data from the random model dataset):

# Synergy Biomarkers for cell proliferation models
random.synergy.analysis.res = list()

for (cell.line in cell.lines) {
  random.synergy.analysis.res[[cell.line]] =
    biomarker_synergy_analysis(random.model.predictions, 
      random.models.stable.state, random.models.link.operator, 
      observed.synergies.per.cell.line[[cell.line]], threshold = 0.7)
}

Observed synergies

Each of the cell lines studied has a different set of observed synergies (drug combinations that were found synergistic across all the 153 tested ones). In this section, we will visualize the cell lines’ observed synergies and mark the synergies that were also predicted by the cell-specific models and the random-generated ones. First, we get the biomarkers for these synergies from each cell line:

total.predicted.synergies.cell.specific =
  unique(unlist(sapply(cell.specific.synergy.analysis.res, function(x) { x$predicted.synergies})))
total.predicted.synergies.cell.specific.num = length(total.predicted.synergies.cell.specific)

The same for the random models:

total.predicted.synergies.random = 
  unique(unlist(sapply(random.synergy.analysis.res, function(x) { x$predicted.synergies})))
total.predicted.synergies.random.num = length(total.predicted.synergies.random)

Then, we get the observed synergies from each cell line in a data.frame:

observed.synergies.res = get_observed_synergies_per_cell_line(cell.line.dirs, drug.combos)

# remove drug combinations which are not observed in any of the cell lines
observed.synergies.res = prune_columns_from_df(observed.synergies.res, value = 0)

total.observed.synergies = colnames(observed.synergies.res)
total.observed.synergies.num = length(total.observed.synergies)

Lastly, we visualize the observed and predicted synergies for all cell lines in one heatmap:

# color the cell-specific predicted synergies
predicted.synergies.colors = rep("black", total.observed.synergies.num)
names(predicted.synergies.colors) = total.observed.synergies
common.predicted.synergies = intersect(total.predicted.synergies.cell.specific,
                                       total.predicted.synergies.random)
cell.specific.only.predicted.synergies = 
  total.predicted.synergies.cell.specific[!total.predicted.synergies.cell.specific %in% total.predicted.synergies.random]
random.only.predicted.synergies = 
  total.predicted.synergies.random[!total.predicted.synergies.random %in% total.predicted.synergies.cell.specific]

predicted.synergies.colors[total.observed.synergies %in% 
                           common.predicted.synergies] = "blue"
predicted.synergies.colors[total.observed.synergies %in% 
                           cell.specific.only.predicted.synergies] = "orange"
predicted.synergies.colors[total.observed.synergies %in% 
                           random.only.predicted.synergies] = "purple"

# define a coloring
obs.synergies.col.fun = colorRamp2(c(0, 1), c("red", "green"))

observed.synergies.heatmap = 
  Heatmap(matrix = as.matrix(observed.synergies.res), 
          col = obs.synergies.col.fun,
          column_title = "Observed synergies per cell line",
          column_title_gp = gpar(fontsize = 20),
          column_names_gp = gpar(col = predicted.synergies.colors),
          row_title = "Cell Lines", row_title_side = "left",
          row_dend_side = "right", row_names_side = "left",
          rect_gp = gpar(col = "black", lwd = 0.3),
          heatmap_legend_param = list(at = c(1, 0), labels = c("YES", "NO"), 
            color_bar = "discrete", title = "Observed", direction = "vertical"))

lgd = Legend(at = c("Cell-specific", "Random", "Both"), title = "Predicted", 
             legend_gp = gpar(fill = c("orange", "purple", "blue")))

draw(observed.synergies.heatmap,  heatmap_legend_list = list(lgd), 
     heatmap_legend_side = "right")

  • The cell-specific models predicted 27 of the 40 observed synergies found across the 8 cell lines, whereas the random models predicted 27 of the them. Thus, the total true positive coverage for all the models across all cell lines is 72.5%
  • Note that there exist synergies which were observed in all cell lines (AK-BI, PI-D1)
  • AK-G4 and 5Z-D1 are observed synergies that only the cell-specific models could predict, whereas the G2-P5 and PI-D4 are observed synergies that only the random models could predict. This shows us that a complimentary approach is needed when searching for biomarkers as the two different kind of models (trained to a specific activity state profile vs trained to proliferation) although they share common true positives regarding the synergies they predict, there are also synergies only a specific class of models could predict.
  • The two synergies of interest, AK-PD and PD-PI, were observed in the A498 cell line and predicted by both the cell-specific and random models.

AK-PD biomarkers

The AK-PD synergy was predicted by both the cell specific and random models in the A498 cell line.

Cell-specific

We get the average state and link operator differences per network node for the A498 cell line from the cell-specific models:

AK.PD.avg.state.diff.cell.specific = cell.specific.synergy.analysis.res$A498$diff.state.synergies.mat["AK-PD", ]
AK.PD.avg.link.diff.cell.specific  = cell.specific.synergy.analysis.res$A498$diff.link.synergies.mat["AK-PD", ]

We build the network from the topology file:

topology.file = paste0(getwd(), "/topology")
net = construct_network(topology.file = topology.file, models.dir =  paste0(getwd(), "/AGS/models"))

# a static layout for plotting the same network always
coordinates.file = paste0(getwd(), "/network_xy_coordinates")
nice.layout = as.matrix(read.table(coordinates.file))

We will now visualize the nodes average state differences in a network graph. Note that the good models are those that predicted the AK-PD drug combination to be synergistic and were contrasted to those that predicted it to be antagonistic (bad models). The number of models in each category were:

model.predictions = model.predictions.per.cell.line[["A498"]]
models.stable.state = models.stable.state.per.cell.line[["A498"]]
drug.comb = "AK-PD"

good.models.num = sum(model.predictions[, drug.comb] == 1 & !is.na(model.predictions[, drug.comb]))
bad.models.num  = sum(model.predictions[, drug.comb] == 0 & !is.na(model.predictions[, drug.comb]))

pretty_print_string(paste0("Number of models (AK-PD synergistic) in the A498 cell line: ", good.models.num))

Number of models (AK-PD synergistic) in the A498 cell line: 26

pretty_print_string(paste0("Number of models (AK-PD antagonistic) in the A498 cell line: ", bad.models.num))

Number of models (AK-PD antagonistic) in the A498 cell line: 2672

plot_avg_state_diff_graph(net, diff = AK.PD.avg.state.diff.cell.specific, 
  layout = nice.layout, title = "AK-PD activity state biomarkers (Cell specific models - A498)")

Thus, we can identify the active state biomarkers:

AK.PD.active.biomarkers = AK.PD.avg.state.diff.cell.specific[AK.PD.avg.state.diff.cell.specific > 0.7]
pretty_print_vector_names(AK.PD.active.biomarkers)

1 node: ERK_f

So, the AK-PD synergy manifests in cancer cell models that have the ERK_f family logical node in an active state. The MAPK-ERK signaling pathway has been studied a lot and has been found to be overexpressed/have increased activity in cancers and as such cancer treatments that include the inhibition of that pathway are found to be most beneficial.

Paper evidence for ERK overexpression in cancer:

The inhibited state biomarkers are:

AK.PD.inhibited.biomarkers = AK.PD.avg.state.diff.cell.specific[AK.PD.avg.state.diff.cell.specific < -0.7]
pretty_print_vector_names(AK.PD.inhibited.biomarkers)

2 nodes: PTPN11, GAB_f

If we check the logical equations related to the above biomarkers we see that:

pretty_print_string("ERK_f *=  (  MEK_f ) AND/OR NOT  (  ( DUSP6 )  or PPP1CA )")

ERK_f *= ( MEK_f ) AND/OR NOT ( ( DUSP6 ) or PPP1CA )

pretty_print_string("GAB_f *=  (  GRB2 ) AND/OR NOT ( ERK_f )")

GAB_f *= ( GRB2 ) AND/OR NOT ( ERK_f )

pretty_print_string("PTPN11 *=  (  GAB_f )")

PTPN11 *= ( GAB_f )

So, pretty much if the GAB_f node is more inhibited in the models that predicted the AK-PD synergy (good models), then PTPN11 is also as well. Also the average activity difference of the GRB2 node is -0.3434189, which makes the GAB_f node more inhibited in the good models since it’s activity is mostly dependent on the ERK_f node, which is mostly activated in the good models. All in all, the overexpression of ERK_f is what causes the two other inhibited biomarkers.

We also visualize the nodes average link operator differences in a network graph:

plot_avg_link_operator_diff_graph(net, diff = AK.PD.avg.link.diff.cell.specific, 
  layout = nice.layout, title = "AK-PD link operator biomarkers (Cell specific models - A498)")

So, the models that predicted the AK-PD synergy, had the OR NOT as a link operator in the boolean equation that has the ERK_f node as target, i.e. ERK_f *= ( MEK_f ) OR NOT ( ( DUSP6 ) or PPP1CA ) instead of ERK_f *= ( MEK_f ) AND NOT ( ( DUSP6 ) or PPP1CA ). The difference in the result of the logical equation can be seen in the next two truth tables where the use of the OR NOT link operator makes the end truth value more flexible in the sense that a lot more more TRUE values are possible, since the activating regulator MEK_f (which is the PD drug’s target) has more weight in the outcome:

Thus, in cancer boolean models where the ERK_f node is overexpressed and the MEK_f logical node is it’s most crucial regulator, inhibiting both the MAPK/ERK pathway (drug PD) and the AKT pathway (drug AK) is a synergistic combination for cancer treatment.

Investigation: Synergy Subsets

It will be interesting to find all the possible synergy sets and subsets that include the AK-PD as the extra synergy. So, models that predict a set of synergies will be contrasted to models that predicted the same set with the addition of the extra AK-PD synergy. Thus we could find synergy biomarkers that allow already (somewhat) good predicting models to predict the additional synergy of interest.

We first construct a matrix, where every row is a set vs subset average activity difference vector of the network nodes:

model.predictions = model.predictions.per.cell.line[["A498"]]
models.stable.state = models.stable.state.per.cell.line[["A498"]]

res = get_synergy_comparison_sets(cell.specific.synergy.analysis.res$A498$synergy.subset.stats)
AK.PD.res = res %>% filter(synergies == "AK-PD")

diff.list = list()
for (i in 1:nrow(AK.PD.res)) {
  synergy.set    = AK.PD.res[i, 2]
  synergy.subset = AK.PD.res[i, 3]
  
  synergy.set.str    = unlist(strsplit(x = synergy.set, split = ","))
  synergy.subset.str = unlist(strsplit(x = synergy.subset, split = ","))
  
  # count models
  synergy.set.models.num = count_models_that_predict_synergies(synergy.set.str, model.predictions)
  synergy.subset.models.num = count_models_that_predict_synergies(synergy.subset.str, model.predictions)
  
  # if too small number of models, skip the diff vector
  if ((synergy.set.models.num <= 3) | (synergy.set.models.num <= 3)) 
    next
  
  # get the diff
  diff.ak.pd = get_avg_activity_diff_based_on_synergy_set_cmp(synergy.set.str, synergy.subset.str, model.predictions, models.stable.state)
  diff.list[[paste0(synergy.set, " vs ", synergy.subset)]] = diff.ak.pd
  
  #plot_avg_state_diff_graph(net, diff = diff.ak.pd, layout = nice.layout, title = paste0("Set: ", synergy.set, "(", synergy.set.models.num, ") vs Subset: ", synergy.subset, "(", synergy.subset.models.num, ")"))
}

diff.mat = do.call(rbind, diff.list)

caption.title = "Table 1: Average Activity Difference Values across all Synergy Set comparisons (AK-PD)"
datatable(data = diff.mat[, c("SRC", "CSK", "MEK_f", "STAT1", "PTPN6")], options = list(
    searching = FALSE, pageLength = 5, lengthMenu = c(5, 10)),
  caption = htmltools::tags$caption(caption.title, style="color:#dd4814; font-size: 18px")) %>% 
  formatRound(1:ncol(diff.mat), digits = 3)

From the above matrix, we count per network node the number of times that the node’s average activity difference value has surpassed a specified threshold (being thus a biomarker) - i.e. the frequency it has been important across all the synergy set comparisons:

threshold = 0.8
biomarker.mat = apply(diff.mat, c(1,2), function(x) {
  if (x >= threshold | x <= -threshold) 1 else 0
})

biomarker.freq = colSums(biomarker.mat)

pretty_print_vector_names_and_values(table(biomarker.freq))

0: 141, 1: 1, 11: 2

The above means that there were \(141\) nodes that were zero times found as biomarkers across all synergy set comparisons, one that was found once and \(2\) that were found 11 times. The two nodes are:

pretty_print_vector_names(biomarker.freq[biomarker.freq == 11])

2 nodes: SRC, PTPN6

For example, visualizing the activity difference of the synergy sets AK-PD,BI-PD,PD-PI vs BI-PD,PD-PI from Table 1:

synergy.set    = "AK-PD,BI-PD,PD-PI"
synergy.subset = "BI-PD,PD-PI"

synergy.set.str    = unlist(strsplit(x = synergy.set, split = ","))
synergy.subset.str = unlist(strsplit(x = synergy.subset, split = ","))

model.predictions = model.predictions.per.cell.line[["A498"]]

# count models
synergy.set.models.num = count_models_that_predict_synergies(synergy.set.str, model.predictions)
synergy.subset.models.num = count_models_that_predict_synergies(synergy.subset.str, model.predictions)

plot_avg_state_diff_graph(net, diff = diff.mat["AK-PD,BI-PD,PD-PI vs BI-PD,PD-PI", ], layout = nice.layout, 
  title = paste0("Set: ", synergy.set, " (", synergy.set.models.num, " models) vs Subset: ", synergy.subset, " (", synergy.subset.models.num, " models)"))

We conclude that in order for the AK-PD drug combination to be synergistic, the cancer model has to have the nodes SRC and PTPN6 in an inhibited state.

Papers supporting the above?

Random

We get the average state and link operator differences per network node for the A498 cell line from the random models:

AK.PD.avg.state.diff.random = random.synergy.analysis.res$A498$diff.state.synergies.mat["AK-PD", ]
AK.PD.avg.link.diff.random  = random.synergy.analysis.res$A498$diff.link.synergies.mat["AK-PD", ]

We will now visualize the nodes average state differences in a network graph. Note that the good models are those that predicted the AK-PD drug combination to be synergistic and were contrasted to those that predicted it to be antagonistic (bad models). The number of models in each category were:

drug.comb = "AK-PD"

good.models.num = sum(random.model.predictions[, drug.comb] == 1 & !is.na(random.model.predictions[, drug.comb]))
bad.models.num  = sum(random.model.predictions[, drug.comb] == 0 & !is.na(random.model.predictions[, drug.comb]))

pretty_print_string(paste0("Number of random models (AK-PD synergistic) in the A498 cell line: ", good.models.num))

Number of random models (AK-PD synergistic) in the A498 cell line: 107

pretty_print_string(paste0("Number of random models (AK-PD antagonistic) in the A498 cell line: ", bad.models.num))

Number of random models (AK-PD antagonistic) in the A498 cell line: 3632

plot_avg_state_diff_graph(net, diff = AK.PD.avg.state.diff.random, 
  layout = nice.layout, title = "AK-PD activity state biomarkers (Random models - A498)")

Thus, we can identify the active state biomarkers:

AK.PD.active.biomarkers = AK.PD.avg.state.diff.random[AK.PD.avg.state.diff.random > 0.7]
pretty_print_vector_names(AK.PD.active.biomarkers)

1 node: ERK_f

There are no inhibited state biomarkers at the specified threshold difference level:

AK.PD.inhibited.biomarkers = AK.PD.avg.state.diff.random[AK.PD.avg.state.diff.random < -0.7]
pretty_print_vector_names(AK.PD.inhibited.biomarkers)

0 nodes:

So, again we observe the overexpression of ERK_f in the models that predicted the AK-PD synergy.

We also visualize the nodes average link operator differences in a network graph:

plot_avg_link_operator_diff_graph(net, diff = AK.PD.avg.link.diff.random, 
  layout = nice.layout, title = "AK-PD link operator biomarkers (Random models - A498)")

The importance of the OR NOT link operator in the boolean equation of ERK_f is again proven to be crucial for the manifestation of the AK-PD synergy, along with the link operators of the equations of the MEK_f, PTEN and PDPK1 nodes.

LS0tCnRpdGxlOiAiQ2FzY2FkZSBTeW5lcmd5IEJpb21hcmtlcnMgZm9yIEFLLVBEIGFuZCBQRC1QSSBzeW5lcmdpZXMiCmF1dGhvcjogIltKb2huIFpvYm9sYXNdKGh0dHBzOi8vZ2l0aHViLmNvbS9iYmxvZGZvbikiCmRhdGU6ICJMYXN0IHVwZGF0ZWQ6IGByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGNzczogc3R5bGUuY3NzCiAgICB0aGVtZTogdW5pdGVkCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICB0b2NfZGVwdGg6IDQKICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIFJlbmRlciBjb21tYW5kLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQojcm1hcmtkb3duOjpyZW5kZXIoaW5wdXQgPSAiLi9jYXNjYWRlX3N5bmVyZ3lfYmlvbWFya2Vycy5SbWQiLCBvdXRwdXRfZm9ybWF0ID0gImh0bWxfZG9jdW1lbnQiLCBvdXRwdXRfZGlyID0gIi4uLy4uL2RvY3MvY2FzY2FkZS9jZWxsLWxpbmVzLTI1MDAvIikKYGBgCgojIyBJbnRybyB7LX0KClRoZSBwdXJwb3NlIG9mIHRoaXMgYW5hbHlzaXMgaXMgdG8gZmluZCBzeW5lcmd5IGJpb21hcmtlcnMgdXNpbmcgdGhlIFtlbWJhIFIgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL2JibG9kZm9uL2VtYmEpIGluIHRoZSBjYXNjYWRlIGJvb2xlYW4gbW9kZWwgZGF0YXNldHMgKHRoZSAqY2FzY2FkZSogaXMgdGhlIG5hbWUgb2YgdGhlIHRvcG9sb2d5IHVzZWQpLiAKTW9yZSBzcGVjaWZpY2FsbHksIHdlIHdpbGwgaW52ZXN0aWdhdGUgdGhlIHR3byBzeW5lcmdpZXM6IGBBSy1QRGAsIGBQRC1QSWAgYW5kIHRyeSB0byBmaW5kIGNhdXNhbCBtZWNoYW5pc21zIHRoYXQgbWlnaHQgZXhwbGFpbiB3aHkgYW5kIHdoZXJlIChwYXRod2F5cykgdGhlc2Ugc3luZXJnaWVzIG1hbmlmZXN0LiAKCk5vdGUgdGhhdCBgQUtgIGlzIGFuICoqQUtUIGluaGliaXRvcioqIGFuZCBgUElgIGlzIGEgKipQSTNLIGluaGliaXRvcioqIGFuZCB0aGV5IGJvdGggdGFyZ2V0IHRoZSAqKlBJM0svQUtUL21UT1IgcGF0aHdheSoqLiBUaGUgY29tYmluYXRpb24gb2Ygc3VjaCBhbiBpbmhpYml0b3Igd2l0aCBhICoqTUVLIGluaGliaXRvcioqIC0gYFBEYCAtIHRoYXQgdGFyZ2V0cyB0aGUgKipNQVBLL0VSSyBwYXRod2F5KiosIGhhcyBwcm92ZW4gdG8gYmUgbW9yZSBlZmZlY3RpdmUgdGhhbiB0aGUgc2luZ2xlIGRydWcgdHJlYXRtZW50IGR1cmluZyBjbGluaWNhbCB0cmlhbHMgd2l0aCBwYXRpZW50cyB0aGF0IGhhZCBhZHZhbmNlZCBjb2xvcmVjdGFsIGNhcmNpbm9tYSAoW3NvdXJjZV0oaHR0cHM6Ly9jbGluaWNhbHRyaWFscy5nb3YvY3QyL3Nob3cvTkNUMDEzMzM0NzUpKS4KClRoZSBib29sZWFuIG1vZGVsIGRhdGFzZXRzIGFyZSBpbiB0b3RhbCAkOSQ6IG9uZSBmb3IgZWFjaCBjZWxsIGxpbmUgCm9mIGludGVyZXN0ICg4IGNlbGwgbGluZXMpIHdoZXJlIHRoZSBtb2RlbHMgd2VyZSAqKmZpdHRlZCB0byBhIHNwZWNpZmljIHN0ZWFkeSBzdGF0ZSoqIGluIGVhY2ggCmNhc2UgYW5kIG9uZSBmb3IgdGhlIHNvLWNhbGxlZCAqKnJhbmRvbSBtb2RlbHMqKiB3aGljaCB3ZXJlIGdlbmVyYXRlZCAqcmFuZG9tbHkqIGluIAp0aGUgc2Vuc2UgdGhhdCB3ZXJlIGZpdHRlZCBvbmx5IHRvIGEgcHJvbGlmZXJhdGlvbiBzdGF0ZSAoc2ltdWxhdGlvbnMgd2VyZSBkb25lIHVzaW5nIAp0aGUgRHJ1Z0xvZ2ljcyBzb2Z0d2FyZSBtb2R1bGVzIGBHaXRzYmVgIGFuZCBgRHJhYm1lYCkuCgpgYGB7ciBMb2FkIGxpYnJhcmllcywgbWVzc2FnZSA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CmxpYnJhcnkoZW1iYSkKbGlicmFyeSh1c2VmdW4pCmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmxpYnJhcnkoY2lyY2xpemUpCmxpYnJhcnkoRFQpCmBgYAoKIyMgSW5wdXQgey19CgpGaXJzdCB3ZSBsb2FkIHRoZSBjZWxsLXNwZWNpZmljIGlucHV0IGRhdGE6CmBgYHtyIENlbGwtc3BlY2lmaWMgSW5wdXQsIGNhY2hlPVRSVUV9CiMgQ2VsbCBMaW5lcwpjZWxsLmxpbmVzID0gYygiQTQ5OCIsICJBR1MiLCAiRFUxNDUiLCAiY29sbzIwNSIsICJTVzYyMCIsICJTRjI5NSIsICJVQUNDNjIiLCAiTURBLU1CLTQ2OCIpCgpjZWxsLmxpbmUuZGlycyA9IHNhcHBseShjZWxsLmxpbmVzLCBmdW5jdGlvbihjZWxsLmxpbmUpIHsKICBwYXN0ZTAoZ2V0d2QoKSwgIi8iLCBjZWxsLmxpbmUpCn0pCgojIE1vZGVsIHByZWRpY3Rpb25zCm1vZGVsLnByZWRpY3Rpb25zLmZpbGVzID0gc2FwcGx5KGNlbGwubGluZS5kaXJzLCBmdW5jdGlvbihjZWxsLmxpbmUuZGlyKSB7CiAgcGFzdGUwKGNlbGwubGluZS5kaXIsICIvbW9kZWxfcHJlZGljdGlvbnMiKQp9KQoKbW9kZWwucHJlZGljdGlvbnMucGVyLmNlbGwubGluZSA9IGxhcHBseShtb2RlbC5wcmVkaWN0aW9ucy5maWxlcywgZnVuY3Rpb24oZmlsZSkgeyAgIAogIGdldF9tb2RlbF9wcmVkaWN0aW9ucyhmaWxlKSAKfSkKCiMgT2JzZXJ2ZWQgc3luZXJnaWVzCm9ic2VydmVkLnN5bmVyZ2llcy5maWxlcyA9IHNhcHBseShjZWxsLmxpbmUuZGlycywgZnVuY3Rpb24oY2VsbC5saW5lLmRpcikgewogIHBhc3RlMChjZWxsLmxpbmUuZGlyLCAiL29ic2VydmVkX3N5bmVyZ2llcyIpCn0pCgpvYnNlcnZlZC5zeW5lcmdpZXMucGVyLmNlbGwubGluZSA9IGxhcHBseShvYnNlcnZlZC5zeW5lcmdpZXMuZmlsZXMsIGZ1bmN0aW9uKGZpbGUpIHsKICBnZXRfb2JzZXJ2ZWRfc3luZXJnaWVzKGZpbGUpCn0pCgojIE1vZGVscyBTdGFibGUgU3RhdGUgKDEgcGVyIG1vZGVsKQptb2RlbHMuc3RhYmxlLnN0YXRlLmZpbGVzID0gc2FwcGx5KGNlbGwubGluZS5kaXJzLCBmdW5jdGlvbihjZWxsLmxpbmUuZGlyKSB7CiAgcGFzdGUwKGNlbGwubGluZS5kaXIsICIvbW9kZWxzX3N0YWJsZV9zdGF0ZSIpCn0pCgptb2RlbHMuc3RhYmxlLnN0YXRlLnBlci5jZWxsLmxpbmUgPSBsYXBwbHkobW9kZWxzLnN0YWJsZS5zdGF0ZS5maWxlcywgZnVuY3Rpb24oZmlsZSkgeyAKICBhcy5tYXRyaXgocmVhZC50YWJsZShmaWxlLCBjaGVjay5uYW1lcyA9IEZBTFNFKSkKfSkKCiMgTW9kZWxzIExpbmsgT3BlcmF0b3JzCm1vZGVscy5saW5rLm9wZXJhdG9yLmZpbGVzID0gc2FwcGx5KGNlbGwubGluZS5kaXJzLCBmdW5jdGlvbihjZWxsLmxpbmUuZGlyKSB7CiAgcGFzdGUwKGNlbGwubGluZS5kaXIsICIvbW9kZWxzX2xpbmtfb3BlcmF0b3IiKQp9KQoKbW9kZWxzLmxpbmsub3BlcmF0b3JzLnBlci5jZWxsLmxpbmUgPSBsYXBwbHkobW9kZWxzLmxpbmsub3BlcmF0b3IuZmlsZXMsIGZ1bmN0aW9uKGZpbGUpIHsKICBhcy5tYXRyaXgocmVhZC50YWJsZShmaWxlLCBjaGVjay5uYW1lcyA9IEZBTFNFKSkKfSkKYGBgCgpUaGUgcmFuZG9tIG1vZGVsIGlucHV0IGRhdGE6CmBgYHtyIFJhbmRvbSBtb2RlbCBJbnB1dH0KcmFuZG9tLmRpciA9IHBhc3RlMChnZXR3ZCgpLCAiL3JhbmRvbSIpCnJhbmRvbS5tb2RlbC5wcmVkaWN0aW9ucyA9IGdldF9tb2RlbF9wcmVkaWN0aW9ucyhwYXN0ZTAocmFuZG9tLmRpciwgIi9tb2RlbF9wcmVkaWN0aW9ucyIpKQoKcmFuZG9tLm1vZGVscy5zdGFibGUuc3RhdGUgPSBhcy5tYXRyaXgoCiAgcmVhZC50YWJsZShmaWxlID0gcGFzdGUwKHJhbmRvbS5kaXIsICIvbW9kZWxzX3N0YWJsZV9zdGF0ZSIpLCBjaGVjay5uYW1lcyA9IEZBTFNFKQopCgpyYW5kb20ubW9kZWxzLmxpbmsub3BlcmF0b3IgPQogIGFzLm1hdHJpeChyZWFkLnRhYmxlKGZpbGUgPSBwYXN0ZTAocmFuZG9tLmRpciwgIi9tb2RlbHNfbGlua19vcGVyYXRvciIpLCBjaGVjay5uYW1lcyA9IEZBTFNFKSkKCiMgdGhlIG5vZGUgbmFtZXMgdXNlZCBpbiBvdXIgYW5hbHlzaXMKbm9kZS5uYW1lcyA9IGNvbG5hbWVzKHJhbmRvbS5tb2RlbHMuc3RhYmxlLnN0YXRlKQojIHRoZSB0ZXN0ZWQgZHJ1ZyBjb21iaW5hdGlvbnMKZHJ1Zy5jb21ib3MgPSBjb2xuYW1lcyhyYW5kb20ubW9kZWwucHJlZGljdGlvbnMpCmBgYAoKIyMgU3luZXJneSBCaW9tYXJrZXIgQW5hbHlzaXMgey19CgpVc2luZyB0aGUgZ2VuZXJpYyBmdW5jdGlvbiBgYmlvbWFya2VyX3N5bmVyZ3lfYW5hbHlzaXNgIGZyb20gdGhlIFtlbWJhIFIgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL2JibG9kZm9uL2VtYmEpLCB3ZSBjYW4gZmluZAoqc3luZXJneSBiaW9tYXJrZXJzKiBpLmUuIG5vZGVzIHdob3NlIGFjdGl2aXR5IGFuZCBib29sZWFuIGVxdWF0aW9uIHBhcmFtZXRlcml6YXRpb24gKGxpbmsgb3BlcmF0b3IpIGFmZmVjdCB0aGUgbWFuaWZlc3RhdGlvbiBvZiBzeW5lcmdpZXMgaW4gdGhlIGJvb2xlYW4gbW9kZWxzLiBNb2RlbHMgYXJlIGNsYXNzaWZpZWQgYmFzZWQgb24gd2hldGhlciB0aGV5IHByZWRpY3Qgb3Igbm90IGVhY2ggb2YgdGhlIHByZWRpY3RlZCBzeW5lcmdpZXMgZm91bmQgaW4gZWFjaCBib29sZWFuIGRhdGFzZXQuCgpGaXJzdCB3ZSBydW4gdGhlIGFuYWx5c2lzIG9uIHRoZSAqKmNlbGwtc3BlY2lmaWMgYm9vbGVhbiBtb2RlbCBkYXRhc2V0cyoqIChub3RlIAp0aGF0IGV2ZXJ5IGlucHV0IHRvIHRoZSBgYmlvbWFya2VyX3N5bmVyZ3lfYW5hbHlzaXNgIGZ1bmN0aW9uIGNoYW5nZXMgcGVyIGNlbGwgbGluZSk6CmBgYHtyIENlbGwtc3BlY2lmaWMgTW9kZWxzIC0gU3luZXJneSBCaW9tYXJrZXIgQW5hbHlzaXMsIGNhY2hlID0gVFJVRX0KY2VsbC5zcGVjaWZpYy5zeW5lcmd5LmFuYWx5c2lzLnJlcyA9IGxpc3QoKQoKZm9yIChjZWxsLmxpbmUgaW4gY2VsbC5saW5lcykgewogIGNlbGwuc3BlY2lmaWMuc3luZXJneS5hbmFseXNpcy5yZXNbW2NlbGwubGluZV1dID0KICAgIGJpb21hcmtlcl9zeW5lcmd5X2FuYWx5c2lzKG1vZGVsLnByZWRpY3Rpb25zLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dLAogICAgICBtb2RlbHMuc3RhYmxlLnN0YXRlLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dLCAKICAgICAgbW9kZWxzLmxpbmsub3BlcmF0b3JzLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dLAogICAgICBvYnNlcnZlZC5zeW5lcmdpZXMucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0sIHRocmVzaG9sZCA9IDAuNykKfQpgYGAKCk5leHQgd2UgcnVuIHRoZSBhbmFseXNpcyBvbiB0aGUgKipyYW5kb20gYm9vbGVhbiBtb2RlbCBkYXRhc2V0cyoqIChub3RlIAp0aGF0IHRoZSBvbmx5IGlucHV0IHRvIHRoZSBgYmlvbWFya2VyX3N5bmVyZ3lfYW5hbHlzaXNgIGZ1bmN0aW9uIHRoYXQgY2hhbmdlcwppcyB0aGUgb2JzZXJ2ZWQgc3luZXJnaWVzIHBlciBjZWxsIGxpbmUgLSB0aGUgcmVzdCBpcyB0aGUgc2FtZSBkYXRhIGZyb20gdGhlIApyYW5kb20gbW9kZWwgZGF0YXNldCk6CmBgYHtyIFJhbmRvbSBNb2RlbHMgLSBTeW5lcmd5IEJpb21hcmtlciBBbmFseXNpcywgY2FjaGUgPSBUUlVFfQojIFN5bmVyZ3kgQmlvbWFya2VycyBmb3IgY2VsbCBwcm9saWZlcmF0aW9uIG1vZGVscwpyYW5kb20uc3luZXJneS5hbmFseXNpcy5yZXMgPSBsaXN0KCkKCmZvciAoY2VsbC5saW5lIGluIGNlbGwubGluZXMpIHsKICByYW5kb20uc3luZXJneS5hbmFseXNpcy5yZXNbW2NlbGwubGluZV1dID0KICAgIGJpb21hcmtlcl9zeW5lcmd5X2FuYWx5c2lzKHJhbmRvbS5tb2RlbC5wcmVkaWN0aW9ucywgCiAgICAgIHJhbmRvbS5tb2RlbHMuc3RhYmxlLnN0YXRlLCByYW5kb20ubW9kZWxzLmxpbmsub3BlcmF0b3IsIAogICAgICBvYnNlcnZlZC5zeW5lcmdpZXMucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0sIHRocmVzaG9sZCA9IDAuNykKfQpgYGAKCiMjIE9ic2VydmVkIHN5bmVyZ2llcyB7LX0KCkVhY2ggb2YgdGhlIGNlbGwgbGluZXMgc3R1ZGllZCBoYXMgYSBkaWZmZXJlbnQgc2V0IG9mIG9ic2VydmVkIHN5bmVyZ2llcyAoZHJ1ZyAKY29tYmluYXRpb25zIHRoYXQgd2VyZSBmb3VuZCBzeW5lcmdpc3RpYyBhY3Jvc3MgYWxsIHRoZSAKYHIgbGVuZ3RoKGRydWcuY29tYm9zKWAgdGVzdGVkIG9uZXMpLiBJbiB0aGlzIHNlY3Rpb24sIHdlIHdpbGwgCioqdmlzdWFsaXplIHRoZSBjZWxsIGxpbmVzJyBvYnNlcnZlZCBzeW5lcmdpZXMgYW5kIG1hcmsgdGhlIHN5bmVyZ2llcyB0aGF0IHdlcmUgCmFsc28gcHJlZGljdGVkIGJ5IHRoZSBjZWxsLXNwZWNpZmljIG1vZGVscyBhbmQgdGhlIHJhbmRvbS1nZW5lcmF0ZWQgb25lcyoqLiAKRmlyc3QsIHdlIGdldCB0aGUgYmlvbWFya2VycyBmb3IgdGhlc2Ugc3luZXJnaWVzIGZyb20gZWFjaCBjZWxsIGxpbmU6CmBgYHtyIFRvdGFsIHByZWRpY3RlZCBzeW5lcmdpZXMgYnkgdGhlIGNlbGwtc3BlY2lmaWMgbW9kZWxzfQp0b3RhbC5wcmVkaWN0ZWQuc3luZXJnaWVzLmNlbGwuc3BlY2lmaWMgPQogIHVuaXF1ZSh1bmxpc3Qoc2FwcGx5KGNlbGwuc3BlY2lmaWMuc3luZXJneS5hbmFseXNpcy5yZXMsIGZ1bmN0aW9uKHgpIHsgeCRwcmVkaWN0ZWQuc3luZXJnaWVzfSkpKQp0b3RhbC5wcmVkaWN0ZWQuc3luZXJnaWVzLmNlbGwuc3BlY2lmaWMubnVtID0gbGVuZ3RoKHRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMuY2VsbC5zcGVjaWZpYykKYGBgCgpUaGUgc2FtZSBmb3IgdGhlIHJhbmRvbSBtb2RlbHM6CmBgYHtyIFRvdGFsIHByZWRpY3RlZCBzeW5lcmdpZXMgYnkgdGhlIHJhbmRvbSBtb2RlbHN9CnRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMucmFuZG9tID0gCiAgdW5pcXVlKHVubGlzdChzYXBwbHkocmFuZG9tLnN5bmVyZ3kuYW5hbHlzaXMucmVzLCBmdW5jdGlvbih4KSB7IHgkcHJlZGljdGVkLnN5bmVyZ2llc30pKSkKdG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5yYW5kb20ubnVtID0gbGVuZ3RoKHRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMucmFuZG9tKQpgYGAKClRoZW4sIHdlIGdldCB0aGUgb2JzZXJ2ZWQgc3luZXJnaWVzIGZyb20gZWFjaCBjZWxsIGxpbmUgaW4gYSBgZGF0YS5mcmFtZWA6CmBgYHtyIE9ic2VydmVkIHN5bmVyZ2llcyBwZXIgY2VsbCBsaW5lfQpvYnNlcnZlZC5zeW5lcmdpZXMucmVzID0gZ2V0X29ic2VydmVkX3N5bmVyZ2llc19wZXJfY2VsbF9saW5lKGNlbGwubGluZS5kaXJzLCBkcnVnLmNvbWJvcykKCiMgcmVtb3ZlIGRydWcgY29tYmluYXRpb25zIHdoaWNoIGFyZSBub3Qgb2JzZXJ2ZWQgaW4gYW55IG9mIHRoZSBjZWxsIGxpbmVzCm9ic2VydmVkLnN5bmVyZ2llcy5yZXMgPSBwcnVuZV9jb2x1bW5zX2Zyb21fZGYob2JzZXJ2ZWQuc3luZXJnaWVzLnJlcywgdmFsdWUgPSAwKQoKdG90YWwub2JzZXJ2ZWQuc3luZXJnaWVzID0gY29sbmFtZXMob2JzZXJ2ZWQuc3luZXJnaWVzLnJlcykKdG90YWwub2JzZXJ2ZWQuc3luZXJnaWVzLm51bSA9IGxlbmd0aCh0b3RhbC5vYnNlcnZlZC5zeW5lcmdpZXMpCmBgYAoKTGFzdGx5LCB3ZSB2aXN1YWxpemUgdGhlIG9ic2VydmVkIGFuZCBwcmVkaWN0ZWQgc3luZXJnaWVzIGZvciBhbGwgY2VsbCBsaW5lcyAKaW4gb25lIGhlYXRtYXA6CmBgYHtyIE9ic2VydmVkIHN5bmVyZ2llcyBoZWF0bWFwLCBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoID0gOSwgZHBpID0gMzAwLCBjYWNoZSA9IFRSVUV9CiMgY29sb3IgdGhlIGNlbGwtc3BlY2lmaWMgcHJlZGljdGVkIHN5bmVyZ2llcwpwcmVkaWN0ZWQuc3luZXJnaWVzLmNvbG9ycyA9IHJlcCgiYmxhY2siLCB0b3RhbC5vYnNlcnZlZC5zeW5lcmdpZXMubnVtKQpuYW1lcyhwcmVkaWN0ZWQuc3luZXJnaWVzLmNvbG9ycykgPSB0b3RhbC5vYnNlcnZlZC5zeW5lcmdpZXMKY29tbW9uLnByZWRpY3RlZC5zeW5lcmdpZXMgPSBpbnRlcnNlY3QodG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5jZWxsLnNwZWNpZmljLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbC5wcmVkaWN0ZWQuc3luZXJnaWVzLnJhbmRvbSkKY2VsbC5zcGVjaWZpYy5vbmx5LnByZWRpY3RlZC5zeW5lcmdpZXMgPSAKICB0b3RhbC5wcmVkaWN0ZWQuc3luZXJnaWVzLmNlbGwuc3BlY2lmaWNbIXRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMuY2VsbC5zcGVjaWZpYyAlaW4lIHRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMucmFuZG9tXQpyYW5kb20ub25seS5wcmVkaWN0ZWQuc3luZXJnaWVzID0gCiAgdG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5yYW5kb21bIXRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMucmFuZG9tICVpbiUgdG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5jZWxsLnNwZWNpZmljXQoKcHJlZGljdGVkLnN5bmVyZ2llcy5jb2xvcnNbdG90YWwub2JzZXJ2ZWQuc3luZXJnaWVzICVpbiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbW1vbi5wcmVkaWN0ZWQuc3luZXJnaWVzXSA9ICJibHVlIgpwcmVkaWN0ZWQuc3luZXJnaWVzLmNvbG9yc1t0b3RhbC5vYnNlcnZlZC5zeW5lcmdpZXMgJWluJSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5zcGVjaWZpYy5vbmx5LnByZWRpY3RlZC5zeW5lcmdpZXNdID0gIm9yYW5nZSIKcHJlZGljdGVkLnN5bmVyZ2llcy5jb2xvcnNbdG90YWwub2JzZXJ2ZWQuc3luZXJnaWVzICVpbiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmRvbS5vbmx5LnByZWRpY3RlZC5zeW5lcmdpZXNdID0gInB1cnBsZSIKCiMgZGVmaW5lIGEgY29sb3JpbmcKb2JzLnN5bmVyZ2llcy5jb2wuZnVuID0gY29sb3JSYW1wMihjKDAsIDEpLCBjKCJyZWQiLCAiZ3JlZW4iKSkKCm9ic2VydmVkLnN5bmVyZ2llcy5oZWF0bWFwID0gCiAgSGVhdG1hcChtYXRyaXggPSBhcy5tYXRyaXgob2JzZXJ2ZWQuc3luZXJnaWVzLnJlcyksIAogICAgICAgICAgY29sID0gb2JzLnN5bmVyZ2llcy5jb2wuZnVuLAogICAgICAgICAgY29sdW1uX3RpdGxlID0gIk9ic2VydmVkIHN5bmVyZ2llcyBwZXIgY2VsbCBsaW5lIiwKICAgICAgICAgIGNvbHVtbl90aXRsZV9ncCA9IGdwYXIoZm9udHNpemUgPSAyMCksCiAgICAgICAgICBjb2x1bW5fbmFtZXNfZ3AgPSBncGFyKGNvbCA9IHByZWRpY3RlZC5zeW5lcmdpZXMuY29sb3JzKSwKICAgICAgICAgIHJvd190aXRsZSA9ICJDZWxsIExpbmVzIiwgcm93X3RpdGxlX3NpZGUgPSAibGVmdCIsCiAgICAgICAgICByb3dfZGVuZF9zaWRlID0gInJpZ2h0Iiwgcm93X25hbWVzX3NpZGUgPSAibGVmdCIsCiAgICAgICAgICByZWN0X2dwID0gZ3Bhcihjb2wgPSAiYmxhY2siLCBsd2QgPSAwLjMpLAogICAgICAgICAgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KGF0ID0gYygxLCAwKSwgbGFiZWxzID0gYygiWUVTIiwgIk5PIiksIAogICAgICAgICAgICBjb2xvcl9iYXIgPSAiZGlzY3JldGUiLCB0aXRsZSA9ICJPYnNlcnZlZCIsIGRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIpKQoKbGdkID0gTGVnZW5kKGF0ID0gYygiQ2VsbC1zcGVjaWZpYyIsICJSYW5kb20iLCAiQm90aCIpLCB0aXRsZSA9ICJQcmVkaWN0ZWQiLCAKICAgICAgICAgICAgIGxlZ2VuZF9ncCA9IGdwYXIoZmlsbCA9IGMoIm9yYW5nZSIsICJwdXJwbGUiLCAiYmx1ZSIpKSkKCmRyYXcob2JzZXJ2ZWQuc3luZXJnaWVzLmhlYXRtYXAsICBoZWF0bWFwX2xlZ2VuZF9saXN0ID0gbGlzdChsZ2QpLCAKICAgICBoZWF0bWFwX2xlZ2VuZF9zaWRlID0gInJpZ2h0IikKYGBgCgo8ZGl2IGNsYXNzID0gImJsdWUtYm94Ij4KLSBUaGUgKmNlbGwtc3BlY2lmaWMqIG1vZGVscyBwcmVkaWN0ZWQgYHIgdG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5jZWxsLnNwZWNpZmljLm51bWAgCm9mIHRoZSBgciB0b3RhbC5vYnNlcnZlZC5zeW5lcmdpZXMubnVtYCBvYnNlcnZlZCBzeW5lcmdpZXMgZm91bmQgYWNyb3NzIHRoZSAKYHIgbGVuZ3RoKGNlbGwubGluZXMpYCBjZWxsIGxpbmVzLCB3aGVyZWFzIHRoZSAqcmFuZG9tKiBtb2RlbHMgcHJlZGljdGVkIApgciB0b3RhbC5wcmVkaWN0ZWQuc3luZXJnaWVzLnJhbmRvbS5udW1gIG9mIHRoZSB0aGVtLgpUaHVzLCB0aGUgKip0b3RhbCB0cnVlIHBvc2l0aXZlIGNvdmVyYWdlIGZvciBhbGwgdGhlIG1vZGVscyBhY3Jvc3MgYWxsIGNlbGwgbGluZXMgaXMgCmByIChsZW5ndGgodW5pb24odG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5jZWxsLnNwZWNpZmljLCB0b3RhbC5wcmVkaWN0ZWQuc3luZXJnaWVzLnJhbmRvbSkpL3RvdGFsLm9ic2VydmVkLnN5bmVyZ2llcy5udW0pKjEwMGAlKioKLSBOb3RlIHRoYXQgdGhlcmUgZXhpc3Qgc3luZXJnaWVzIHdoaWNoIHdlcmUgb2JzZXJ2ZWQgaW4gYWxsIGNlbGwgbGluZXMgKGBBSy1CSWAsCmBQSS1EMWApCi0gYEFLLUc0YCBhbmQgYDVaLUQxYCBhcmUgb2JzZXJ2ZWQgc3luZXJnaWVzIHRoYXQgb25seSB0aGUgY2VsbC1zcGVjaWZpYyBtb2RlbHMgY291bGQgcHJlZGljdCwgd2hlcmVhcyB0aGUgYEcyLVA1YCBhbmQgYFBJLUQ0YCBhcmUgb2JzZXJ2ZWQgc3luZXJnaWVzIHRoYXQgb25seSB0aGUgcmFuZG9tIG1vZGVscyBjb3VsZCBwcmVkaWN0LiBUaGlzIHNob3dzIHVzIHRoYXQgYSAqKmNvbXBsaW1lbnRhcnkKYXBwcm9hY2gqKiBpcyBuZWVkZWQgd2hlbiBzZWFyY2hpbmcgZm9yIGJpb21hcmtlcnMgYXMgdGhlIHR3byBkaWZmZXJlbnQga2luZCBvZiAKbW9kZWxzICh0cmFpbmVkIHRvIGEgc3BlY2lmaWMgYWN0aXZpdHkgc3RhdGUgcHJvZmlsZSB2cyB0cmFpbmVkIHRvIHByb2xpZmVyYXRpb24pCmFsdGhvdWdoIHRoZXkgc2hhcmUgY29tbW9uIHRydWUgcG9zaXRpdmVzIHJlZ2FyZGluZyB0aGUgc3luZXJnaWVzIHRoZXkgcHJlZGljdCwKdGhlcmUgYXJlIGFsc28gc3luZXJnaWVzIG9ubHkgYSBzcGVjaWZpYyBjbGFzcyBvZiBtb2RlbHMgY291bGQgcHJlZGljdC4KLSBUaGUgdHdvIHN5bmVyZ2llcyBvZiBpbnRlcmVzdCwgYEFLLVBEYCBhbmQgYFBELVBJYCwgd2VyZSBvYnNlcnZlZCBpbiB0aGUgYEE0OThgCmNlbGwgbGluZSBhbmQgcHJlZGljdGVkIGJ5IGJvdGggdGhlIGNlbGwtc3BlY2lmaWMgYW5kIHJhbmRvbSBtb2RlbHMuCjwvZGl2PgoKIyMgQUstUEQgYmlvbWFya2VycyB7LX0KClRoZSBgQUstUERgIHN5bmVyZ3kgd2FzIHByZWRpY3RlZCBieSBib3RoIHRoZSBjZWxsIHNwZWNpZmljIGFuZCByYW5kb20gbW9kZWxzIGluIHRoZSBgQTQ5OGAgY2VsbCBsaW5lLgoKIyMjIENlbGwtc3BlY2lmaWMgey19CgpXZSBnZXQgdGhlIGF2ZXJhZ2Ugc3RhdGUgYW5kIGxpbmsgb3BlcmF0b3IgZGlmZmVyZW5jZXMgcGVyIG5ldHdvcmsgbm9kZSBmb3IgdGhlIGBBNDk4YCBjZWxsIGxpbmUgZnJvbSB0aGUgY2VsbC1zcGVjaWZpYyBtb2RlbHM6CgpgYGB7ciBBSy1QRCBkaWZmIChDZWxsIHNwZWNpZmljIG1vZGVscyAtIEE0OTgpfQpBSy5QRC5hdmcuc3RhdGUuZGlmZi5jZWxsLnNwZWNpZmljID0gY2VsbC5zcGVjaWZpYy5zeW5lcmd5LmFuYWx5c2lzLnJlcyRBNDk4JGRpZmYuc3RhdGUuc3luZXJnaWVzLm1hdFsiQUstUEQiLCBdCkFLLlBELmF2Zy5saW5rLmRpZmYuY2VsbC5zcGVjaWZpYyAgPSBjZWxsLnNwZWNpZmljLnN5bmVyZ3kuYW5hbHlzaXMucmVzJEE0OTgkZGlmZi5saW5rLnN5bmVyZ2llcy5tYXRbIkFLLVBEIiwgXQpgYGAKCldlIGJ1aWxkIHRoZSBuZXR3b3JrIGZyb20gdGhlIHRvcG9sb2d5IGZpbGU6CgpgYGB7ciBOZXR3b3JrIGJ1aWxkaW5nfQp0b3BvbG9neS5maWxlID0gcGFzdGUwKGdldHdkKCksICIvdG9wb2xvZ3kiKQpuZXQgPSBjb25zdHJ1Y3RfbmV0d29yayh0b3BvbG9neS5maWxlID0gdG9wb2xvZ3kuZmlsZSwgbW9kZWxzLmRpciA9ICBwYXN0ZTAoZ2V0d2QoKSwgIi9BR1MvbW9kZWxzIikpCgojIGEgc3RhdGljIGxheW91dCBmb3IgcGxvdHRpbmcgdGhlIHNhbWUgbmV0d29yayBhbHdheXMKY29vcmRpbmF0ZXMuZmlsZSA9IHBhc3RlMChnZXR3ZCgpLCAiL25ldHdvcmtfeHlfY29vcmRpbmF0ZXMiKQpuaWNlLmxheW91dCA9IGFzLm1hdHJpeChyZWFkLnRhYmxlKGNvb3JkaW5hdGVzLmZpbGUpKQpgYGAKCldlIHdpbGwgbm93IHZpc3VhbGl6ZSB0aGUgbm9kZXMgYXZlcmFnZSBzdGF0ZSBkaWZmZXJlbmNlcyBpbiBhIG5ldHdvcmsgZ3JhcGguIE5vdGUgdGhhdCB0aGUgKipnb29kIG1vZGVscyoqIGFyZSB0aG9zZSB0aGF0IHByZWRpY3RlZCB0aGUgYEFLLVBEYCBkcnVnIGNvbWJpbmF0aW9uIHRvIGJlICpzeW5lcmdpc3RpYyogYW5kIHdlcmUgY29udHJhc3RlZCB0byB0aG9zZSB0aGF0IHByZWRpY3RlZCBpdCB0byBiZSAqYW50YWdvbmlzdGljKiAoKipiYWQgbW9kZWxzKiopLiBUaGUgbnVtYmVyIG9mIG1vZGVscyBpbiBlYWNoIGNhdGVnb3J5IHdlcmU6CgpgYGB7ciBDb3VudCBnb29kIHZzIGJhZCBtb2RlbHMgZm9yIEFLLVBEIHN5bmVyZ3kgKGNlbGwtc3BlY2lmaWMgbW9kZWxzKSwgcmVzdWx0cz0nYXNpcyd9Cm1vZGVsLnByZWRpY3Rpb25zID0gbW9kZWwucHJlZGljdGlvbnMucGVyLmNlbGwubGluZVtbIkE0OTgiXV0KbW9kZWxzLnN0YWJsZS5zdGF0ZSA9IG1vZGVscy5zdGFibGUuc3RhdGUucGVyLmNlbGwubGluZVtbIkE0OTgiXV0KZHJ1Zy5jb21iID0gIkFLLVBEIgoKZ29vZC5tb2RlbHMubnVtID0gc3VtKG1vZGVsLnByZWRpY3Rpb25zWywgZHJ1Zy5jb21iXSA9PSAxICYgIWlzLm5hKG1vZGVsLnByZWRpY3Rpb25zWywgZHJ1Zy5jb21iXSkpCmJhZC5tb2RlbHMubnVtICA9IHN1bShtb2RlbC5wcmVkaWN0aW9uc1ssIGRydWcuY29tYl0gPT0gMCAmICFpcy5uYShtb2RlbC5wcmVkaWN0aW9uc1ssIGRydWcuY29tYl0pKQoKcHJldHR5X3ByaW50X3N0cmluZyhwYXN0ZTAoIk51bWJlciBvZiBtb2RlbHMgKEFLLVBEIHN5bmVyZ2lzdGljKSBpbiB0aGUgQTQ5OCBjZWxsIGxpbmU6ICIsIGdvb2QubW9kZWxzLm51bSkpCnByZXR0eV9wcmludF9zdHJpbmcocGFzdGUwKCJOdW1iZXIgb2YgbW9kZWxzIChBSy1QRCBhbnRhZ29uaXN0aWMpIGluIHRoZSBBNDk4IGNlbGwgbGluZTogIiwgYmFkLm1vZGVscy5udW0pKQpgYGAKCmBgYHtyIEFLLVBEIGFjdGl2aXR5IHN0YXRlIGJpb21hcmtlcnMgKENlbGwgc3BlY2lmaWMgbW9kZWxzIC0gQTQ5OCksIGNhY2hlID0gVFJVRX0KcGxvdF9hdmdfc3RhdGVfZGlmZl9ncmFwaChuZXQsIGRpZmYgPSBBSy5QRC5hdmcuc3RhdGUuZGlmZi5jZWxsLnNwZWNpZmljLCAKICBsYXlvdXQgPSBuaWNlLmxheW91dCwgdGl0bGUgPSAiQUstUEQgYWN0aXZpdHkgc3RhdGUgYmlvbWFya2VycyAoQ2VsbCBzcGVjaWZpYyBtb2RlbHMgLSBBNDk4KSIpCmBgYAoKVGh1cywgd2UgY2FuIGlkZW50aWZ5IHRoZSBhY3RpdmUgc3RhdGUgYmlvbWFya2VyczoKCmBgYHtyIEFLLVBEIGFjdGl2ZSBzdGF0ZSBiaW9tYXJrZXJzIChDZWxsIHNwZWNpZmljIG1vZGVscyAtIEE0OTgpLCByZXN1bHRzID0gJ2FzaXMnfQpBSy5QRC5hY3RpdmUuYmlvbWFya2VycyA9IEFLLlBELmF2Zy5zdGF0ZS5kaWZmLmNlbGwuc3BlY2lmaWNbQUsuUEQuYXZnLnN0YXRlLmRpZmYuY2VsbC5zcGVjaWZpYyA+IDAuN10KcHJldHR5X3ByaW50X3ZlY3Rvcl9uYW1lcyhBSy5QRC5hY3RpdmUuYmlvbWFya2VycykKYGBgCgpTbywgdGhlIGBBSy1QRGAgc3luZXJneSBtYW5pZmVzdHMgaW4gY2FuY2VyIGNlbGwgbW9kZWxzIHRoYXQgaGF2ZSB0aGUgYEVSS19mYCBmYW1pbHkgbG9naWNhbCBub2RlIGluIGFuICoqYWN0aXZlKiogc3RhdGUuClRoZSAqKk1BUEstRVJLIHNpZ25hbGluZyBwYXRod2F5KiogaGFzIGJlZW4gc3R1ZGllZCBhIGxvdCBhbmQgaGFzIGJlZW4gZm91bmQgdG8gYmUgKipvdmVyZXhwcmVzc2VkL2hhdmUgaW5jcmVhc2VkIGFjdGl2aXR5KiogaW4gY2FuY2VycyBhbmQgYXMgc3VjaCBjYW5jZXIgdHJlYXRtZW50cyB0aGF0IGluY2x1ZGUgdGhlIGluaGliaXRpb24gb2YgdGhhdCBwYXRod2F5IGFyZSBmb3VuZCB0byBiZSBtb3N0IGJlbmVmaWNpYWwuCgpQYXBlciBldmlkZW5jZSBmb3IgKipgRVJLYCBvdmVyZXhwcmVzc2lvbioqIGluIGNhbmNlcjogCgotIFtSb2xlcyBvZiB0aGUgUmFmL01FSy9FUksgcGF0aHdheSBpbiBjZWxsIGdyb3d0aCwgbWFsaWduYW50IHRyYW5zZm9ybWF0aW9uIGFuZCBkcnVnIHJlc2lzdGFuY2VdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMDE2L2ouYmJhbWNyLjIwMDYuMTAuMDAxKQotIFtUaGUgUmFzL1JhZi9NRUsvRVJLIHNpZ25hbGluZyBwYXRod2F5IGFuZCBpdHMgcm9sZSBpbiB0aGUgb2NjdXJyZW5jZSBhbmQgZGV2ZWxvcG1lbnQgb2YgSENDIChSZXZpZXcpXShodHRwczovL2RvaS5vcmcvMTAuMzg5Mi9vbC4yMDE2LjUxMTApCi0gW1RhcmdldGluZyBFUkssIGFuIEFjaGlsbGVzJyBIZWVsIG9mIHRoZSBNQVBLIHBhdGh3YXksIGluIGNhbmNlciB0aGVyYXB5XShodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLmFwc2IuMjAxOC4wMS4wMDgpCi0gW0VSSyBpcyBhIFBpdm90YWwgUGxheWVyIG9mIENoZW1vLUltbXVuZS1SZXNpc3RhbmNlIGluIENhbmNlcl0oaHR0cHM6Ly9kb2kub3JnLzEwLjMzOTAvaWptczIwMTAyNTA1KQotIE90aGVycz8/PwoKVGhlIGluaGliaXRlZCBzdGF0ZSBiaW9tYXJrZXJzIGFyZToKCmBgYHtyIEFLLVBEIGluaGliaXRlZCBzdGF0ZSBiaW9tYXJrZXJzIChDZWxsIHNwZWNpZmljIG1vZGVscyAtIEE0OTgpLCByZXN1bHRzID0gJ2FzaXMnfQpBSy5QRC5pbmhpYml0ZWQuYmlvbWFya2VycyA9IEFLLlBELmF2Zy5zdGF0ZS5kaWZmLmNlbGwuc3BlY2lmaWNbQUsuUEQuYXZnLnN0YXRlLmRpZmYuY2VsbC5zcGVjaWZpYyA8IC0wLjddCnByZXR0eV9wcmludF92ZWN0b3JfbmFtZXMoQUsuUEQuaW5oaWJpdGVkLmJpb21hcmtlcnMpCmBgYAoKSWYgd2UgY2hlY2sgdGhlIGxvZ2ljYWwgZXF1YXRpb25zIHJlbGF0ZWQgdG8gdGhlIGFib3ZlIGJpb21hcmtlcnMgd2Ugc2VlIHRoYXQ6CgpgYGB7ciBwcmludCBlcXVhdGlvbnMsIHJlc3VsdHM9J2FzaXMnfQpwcmV0dHlfcHJpbnRfc3RyaW5nKCJFUktfZiAqPSAgKCAgTUVLX2YgKSBBTkQvT1IgTk9UICAoICAoIERVU1A2ICkgIG9yIFBQUDFDQSApIikKcHJldHR5X3ByaW50X3N0cmluZygiR0FCX2YgKj0gICggIEdSQjIgKSBBTkQvT1IgTk9UICggRVJLX2YgKSIpCnByZXR0eV9wcmludF9zdHJpbmcoIlBUUE4xMSAqPSAgKCAgR0FCX2YgKSIpCmBgYAoKU28sIHByZXR0eSBtdWNoIGlmIHRoZSBgR0FCX2ZgIG5vZGUgaXMgbW9yZSBpbmhpYml0ZWQgaW4gdGhlIG1vZGVscyB0aGF0IHByZWRpY3RlZCB0aGUgYEFLLVBEYCBzeW5lcmd5IChnb29kIG1vZGVscyksIHRoZW4gYFBUUE4xMWAgaXMgYWxzbyBhcyB3ZWxsLiAKQWxzbyB0aGUgYXZlcmFnZSBhY3Rpdml0eSBkaWZmZXJlbmNlIG9mIHRoZSBgR1JCMmAgbm9kZSBpcyBgciBBSy5QRC5hdmcuc3RhdGUuZGlmZi5jZWxsLnNwZWNpZmljWyJHUkIyIl1gLCB3aGljaCBtYWtlcyB0aGUgYEdBQl9mYCBub2RlIG1vcmUgaW5oaWJpdGVkIGluIHRoZSBnb29kIG1vZGVscyBzaW5jZSBpdCdzIGFjdGl2aXR5IGlzIG1vc3RseSBkZXBlbmRlbnQgb24gdGhlIGBFUktfZmAgbm9kZSwgd2hpY2ggaXMgbW9zdGx5IGFjdGl2YXRlZCBpbiB0aGUgZ29vZCBtb2RlbHMuCkFsbCBpbiBhbGwsIHRoZSBvdmVyZXhwcmVzc2lvbiBvZiBgRVJLX2ZgIGlzIHdoYXQgY2F1c2VzIHRoZSB0d28gb3RoZXIgaW5oaWJpdGVkIGJpb21hcmtlcnMuCgpXZSBhbHNvIHZpc3VhbGl6ZSB0aGUgbm9kZXMgYXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmZlcmVuY2VzIGluIGEgbmV0d29yayBncmFwaDoKCmBgYHtyIEFLLVBEIGxpbmsgb3BlcmF0b3IgYmlvbWFya2VycyAoQ2VsbCBzcGVjaWZpYyBtb2RlbHMgLSBBNDk4KSwgY2FjaGUgPSBUUlVFfQpwbG90X2F2Z19saW5rX29wZXJhdG9yX2RpZmZfZ3JhcGgobmV0LCBkaWZmID0gQUsuUEQuYXZnLmxpbmsuZGlmZi5jZWxsLnNwZWNpZmljLCAKICBsYXlvdXQgPSBuaWNlLmxheW91dCwgdGl0bGUgPSAiQUstUEQgbGluayBvcGVyYXRvciBiaW9tYXJrZXJzIChDZWxsIHNwZWNpZmljIG1vZGVscyAtIEE0OTgpIikKYGBgCgpTbywgdGhlIG1vZGVscyB0aGF0IHByZWRpY3RlZCB0aGUgYEFLLVBEYCBzeW5lcmd5LCBoYWQgdGhlICoqT1IgTk9UKiogYXMgYSBsaW5rIG9wZXJhdG9yIGluIHRoZSBib29sZWFuIGVxdWF0aW9uIHRoYXQgaGFzIHRoZSBgRVJLX2ZgIG5vZGUgYXMgdGFyZ2V0LCBpLmUuIGBFUktfZiAqPSAgKCAgTUVLX2YgKSBPUiBOT1QgICggICggRFVTUDYgKSAgb3IgUFBQMUNBIClgIGluc3RlYWQgb2YgYEVSS19mICo9ICAoICBNRUtfZiApIEFORCBOT1QgKCAoIERVU1A2ICkgIG9yIFBQUDFDQSApYC4gClRoZSBkaWZmZXJlbmNlIGluIHRoZSByZXN1bHQgb2YgdGhlIGxvZ2ljYWwgZXF1YXRpb24gY2FuIGJlIHNlZW4gaW4gdGhlIG5leHQgdHdvIHRydXRoIHRhYmxlcyB3aGVyZSB0aGUgdXNlIG9mIHRoZSAqKk9SIE5PVCBsaW5rIG9wZXJhdG9yKiogbWFrZXMgdGhlIGVuZCB0cnV0aCB2YWx1ZSBtb3JlICpmbGV4aWJsZSogaW4gdGhlIHNlbnNlIHRoYXQgYSBsb3QgbW9yZSBtb3JlIFRSVUUgdmFsdWVzIGFyZSBwb3NzaWJsZSwgc2luY2UgdGhlIGFjdGl2YXRpbmcgcmVndWxhdG9yIGBNRUtfZmAgKHdoaWNoIGlzIHRoZSBgUERgIGRydWcncyB0YXJnZXQpIGhhcyBtb3JlIHdlaWdodCBpbiB0aGUgb3V0Y29tZToKCiFbXShpbWcvdHJ1dGhfdGFibGVfYW5kLnN2ZykhW10oaW1nL3RydXRoX3RhYmxlX29yLnN2ZykKCjxkaXYgY2xhc3MgPSAiYmx1ZS1ib3giPgpUaHVzLCBpbiBjYW5jZXIgYm9vbGVhbiBtb2RlbHMgd2hlcmUgdGhlIGBFUktfZmAgbm9kZSBpcyBvdmVyZXhwcmVzc2VkIGFuZCB0aGUgYE1FS19mYCBsb2dpY2FsIG5vZGUgaXMgaXQncyBtb3N0IGNydWNpYWwgcmVndWxhdG9yLCBpbmhpYml0aW5nIGJvdGggdGhlICoqTUFQSy9FUksgcGF0aHdheSoqIChkcnVnIGBQRGApIGFuZCB0aGUgKipBS1QgcGF0aHdheSoqIChkcnVnIGBBS2ApIGlzIGEgc3luZXJnaXN0aWMgY29tYmluYXRpb24gZm9yIGNhbmNlciB0cmVhdG1lbnQuCjwvZGl2PgoKIyMjIyBJbnZlc3RpZ2F0aW9uOiBTeW5lcmd5IFN1YnNldHMKCkl0IHdpbGwgYmUgaW50ZXJlc3RpbmcgdG8gZmluZCAqKmFsbCB0aGUgcG9zc2libGUgc3luZXJneSBzZXRzIGFuZCBzdWJzZXRzIHRoYXQgaW5jbHVkZSB0aGUgYEFLLVBEYCBhcyB0aGUgZXh0cmEgc3luZXJneSoqLiAKU28sIG1vZGVscyB0aGF0IHByZWRpY3QgYSBzZXQgb2Ygc3luZXJnaWVzIHdpbGwgYmUgY29udHJhc3RlZCB0byBtb2RlbHMgdGhhdCBwcmVkaWN0ZWQgdGhlIHNhbWUgc2V0IHdpdGggdGhlIGFkZGl0aW9uIG9mIHRoZSBleHRyYSBgQUstUERgIHN5bmVyZ3kuIFRodXMgd2UgY291bGQgZmluZCAqKnN5bmVyZ3kgYmlvbWFya2VycyB0aGF0IGFsbG93IGFscmVhZHkgKHNvbWV3aGF0KSBnb29kIHByZWRpY3RpbmcgbW9kZWxzIHRvIHByZWRpY3QgdGhlIGFkZGl0aW9uYWwgc3luZXJneSBvZiBpbnRlcmVzdCoqLgoKV2UgZmlyc3QgY29uc3RydWN0IGEgbWF0cml4LCB3aGVyZSAqKmV2ZXJ5IHJvdyBpcyBhIHNldCB2cyBzdWJzZXQgYXZlcmFnZSBhY3Rpdml0eSBkaWZmZXJlbmNlIHZlY3RvciBvZiB0aGUgbmV0d29yayBub2RlcyoqOgpgYGB7ciBTeW5lcmd5IFN1YnNldHMgQ29tcGFyaXNvbiBUZXN0IChDZWxsLXNwZWNpZmljIC0gQTQ5OCl9Cm1vZGVsLnByZWRpY3Rpb25zID0gbW9kZWwucHJlZGljdGlvbnMucGVyLmNlbGwubGluZVtbIkE0OTgiXV0KbW9kZWxzLnN0YWJsZS5zdGF0ZSA9IG1vZGVscy5zdGFibGUuc3RhdGUucGVyLmNlbGwubGluZVtbIkE0OTgiXV0KCnJlcyA9IGdldF9zeW5lcmd5X2NvbXBhcmlzb25fc2V0cyhjZWxsLnNwZWNpZmljLnN5bmVyZ3kuYW5hbHlzaXMucmVzJEE0OTgkc3luZXJneS5zdWJzZXQuc3RhdHMpCkFLLlBELnJlcyA9IHJlcyAlPiUgZmlsdGVyKHN5bmVyZ2llcyA9PSAiQUstUEQiKQoKZGlmZi5saXN0ID0gbGlzdCgpCmZvciAoaSBpbiAxOm5yb3coQUsuUEQucmVzKSkgewogIHN5bmVyZ3kuc2V0ICAgID0gQUsuUEQucmVzW2ksIDJdCiAgc3luZXJneS5zdWJzZXQgPSBBSy5QRC5yZXNbaSwgM10KICAKICBzeW5lcmd5LnNldC5zdHIgICAgPSB1bmxpc3Qoc3Ryc3BsaXQoeCA9IHN5bmVyZ3kuc2V0LCBzcGxpdCA9ICIsIikpCiAgc3luZXJneS5zdWJzZXQuc3RyID0gdW5saXN0KHN0cnNwbGl0KHggPSBzeW5lcmd5LnN1YnNldCwgc3BsaXQgPSAiLCIpKQogIAogICMgY291bnQgbW9kZWxzCiAgc3luZXJneS5zZXQubW9kZWxzLm51bSA9IGNvdW50X21vZGVsc190aGF0X3ByZWRpY3Rfc3luZXJnaWVzKHN5bmVyZ3kuc2V0LnN0ciwgbW9kZWwucHJlZGljdGlvbnMpCiAgc3luZXJneS5zdWJzZXQubW9kZWxzLm51bSA9IGNvdW50X21vZGVsc190aGF0X3ByZWRpY3Rfc3luZXJnaWVzKHN5bmVyZ3kuc3Vic2V0LnN0ciwgbW9kZWwucHJlZGljdGlvbnMpCiAgCiAgIyBpZiB0b28gc21hbGwgbnVtYmVyIG9mIG1vZGVscywgc2tpcCB0aGUgZGlmZiB2ZWN0b3IKICBpZiAoKHN5bmVyZ3kuc2V0Lm1vZGVscy5udW0gPD0gMykgfCAoc3luZXJneS5zZXQubW9kZWxzLm51bSA8PSAzKSkgCiAgICBuZXh0CiAgCiAgIyBnZXQgdGhlIGRpZmYKICBkaWZmLmFrLnBkID0gZ2V0X2F2Z19hY3Rpdml0eV9kaWZmX2Jhc2VkX29uX3N5bmVyZ3lfc2V0X2NtcChzeW5lcmd5LnNldC5zdHIsIHN5bmVyZ3kuc3Vic2V0LnN0ciwgbW9kZWwucHJlZGljdGlvbnMsIG1vZGVscy5zdGFibGUuc3RhdGUpCiAgZGlmZi5saXN0W1twYXN0ZTAoc3luZXJneS5zZXQsICIgdnMgIiwgc3luZXJneS5zdWJzZXQpXV0gPSBkaWZmLmFrLnBkCiAgCiAgI3Bsb3RfYXZnX3N0YXRlX2RpZmZfZ3JhcGgobmV0LCBkaWZmID0gZGlmZi5hay5wZCwgbGF5b3V0ID0gbmljZS5sYXlvdXQsIHRpdGxlID0gcGFzdGUwKCJTZXQ6ICIsIHN5bmVyZ3kuc2V0LCAiKCIsIHN5bmVyZ3kuc2V0Lm1vZGVscy5udW0sICIpIHZzIFN1YnNldDogIiwgc3luZXJneS5zdWJzZXQsICIoIiwgc3luZXJneS5zdWJzZXQubW9kZWxzLm51bSwgIikiKSkKfQoKZGlmZi5tYXQgPSBkby5jYWxsKHJiaW5kLCBkaWZmLmxpc3QpCgpjYXB0aW9uLnRpdGxlID0gIlRhYmxlIDE6IEF2ZXJhZ2UgQWN0aXZpdHkgRGlmZmVyZW5jZSBWYWx1ZXMgYWNyb3NzIGFsbCBTeW5lcmd5IFNldCBjb21wYXJpc29ucyAoQUstUEQpIgpkYXRhdGFibGUoZGF0YSA9IGRpZmYubWF0WywgYygiU1JDIiwgIkNTSyIsICJNRUtfZiIsICJTVEFUMSIsICJQVFBONiIpXSwgb3B0aW9ucyA9IGxpc3QoCiAgICBzZWFyY2hpbmcgPSBGQUxTRSwgcGFnZUxlbmd0aCA9IDUsIGxlbmd0aE1lbnUgPSBjKDUsIDEwKSksCiAgY2FwdGlvbiA9IGh0bWx0b29sczo6dGFncyRjYXB0aW9uKGNhcHRpb24udGl0bGUsIHN0eWxlPSJjb2xvcjojZGQ0ODE0OyBmb250LXNpemU6IDE4cHgiKSkgJT4lIAogIGZvcm1hdFJvdW5kKDE6bmNvbChkaWZmLm1hdCksIGRpZ2l0cyA9IDMpCmBgYAoKRnJvbSB0aGUgYWJvdmUgbWF0cml4LCB3ZSBjb3VudCBwZXIgbmV0d29yayBub2RlICoqdGhlIG51bWJlciBvZiB0aW1lcyB0aGF0IHRoZSBub2RlJ3MgYXZlcmFnZSBhY3Rpdml0eSBkaWZmZXJlbmNlIHZhbHVlIGhhcyBzdXJwYXNzZWQgYSBzcGVjaWZpZWQgdGhyZXNob2xkKiogKGJlaW5nIHRodXMgYSBiaW9tYXJrZXIpIC0gaS5lLiB0aGUgZnJlcXVlbmN5IGl0IGhhcyBiZWVuIGltcG9ydGFudCBhY3Jvc3MgYWxsIHRoZSBzeW5lcmd5IHNldCBjb21wYXJpc29uczoKYGBge3IgQmlvbWFya2VyIEZyZXF1ZW5jeSBhY3Jvc3MgYWxsIGNvbXBhcmlzb24gc2V0cyAoQUstUEsgLSBDZWxsLXNwZWNpZmljIC0gQTQ5OCksIHJlc3VsdHM9J2FzaXMnfQp0aHJlc2hvbGQgPSAwLjgKYmlvbWFya2VyLm1hdCA9IGFwcGx5KGRpZmYubWF0LCBjKDEsMiksIGZ1bmN0aW9uKHgpIHsKICBpZiAoeCA+PSB0aHJlc2hvbGQgfCB4IDw9IC10aHJlc2hvbGQpIDEgZWxzZSAwCn0pCgpiaW9tYXJrZXIuZnJlcSA9IGNvbFN1bXMoYmlvbWFya2VyLm1hdCkKCnByZXR0eV9wcmludF92ZWN0b3JfbmFtZXNfYW5kX3ZhbHVlcyh0YWJsZShiaW9tYXJrZXIuZnJlcSkpCmBgYAoKVGhlIGFib3ZlIG1lYW5zIHRoYXQgdGhlcmUgd2VyZSAkMTQxJCBub2RlcyB0aGF0IHdlcmUgKnplcm8qIHRpbWVzIGZvdW5kIGFzIGJpb21hcmtlcnMgYWNyb3NzIGFsbCBzeW5lcmd5IHNldCBjb21wYXJpc29ucywgKipvbmUgdGhhdCB3YXMgZm91bmQgb25jZSBhbmQgJDIkIHRoYXQgd2VyZSBmb3VuZCAxMSB0aW1lcyoqLiBUaGUgdHdvIG5vZGVzIGFyZToKCmBgYHtyIEV4dHJhIGJpb21hcmtlcnM6IEFLLVBELCByZXN1bHRzPSdhc2lzJ30KcHJldHR5X3ByaW50X3ZlY3Rvcl9uYW1lcyhiaW9tYXJrZXIuZnJlcVtiaW9tYXJrZXIuZnJlcSA9PSAxMV0pCmBgYAoKRm9yIGV4YW1wbGUsIHZpc3VhbGl6aW5nIHRoZSBhY3Rpdml0eSBkaWZmZXJlbmNlIG9mIHRoZSBzeW5lcmd5IHNldHMgYEFLLVBELEJJLVBELFBELVBJYCB2cyBgQkktUEQsUEQtUElgIGZyb20gVGFibGUgMToKCmBgYHtyIEFLLVBELEJJLVBELFBELVBJIHZzIEJJLVBELFBELVBJIGdyYXBofQpzeW5lcmd5LnNldCAgICA9ICJBSy1QRCxCSS1QRCxQRC1QSSIKc3luZXJneS5zdWJzZXQgPSAiQkktUEQsUEQtUEkiCgpzeW5lcmd5LnNldC5zdHIgICAgPSB1bmxpc3Qoc3Ryc3BsaXQoeCA9IHN5bmVyZ3kuc2V0LCBzcGxpdCA9ICIsIikpCnN5bmVyZ3kuc3Vic2V0LnN0ciA9IHVubGlzdChzdHJzcGxpdCh4ID0gc3luZXJneS5zdWJzZXQsIHNwbGl0ID0gIiwiKSkKCm1vZGVsLnByZWRpY3Rpb25zID0gbW9kZWwucHJlZGljdGlvbnMucGVyLmNlbGwubGluZVtbIkE0OTgiXV0KCiMgY291bnQgbW9kZWxzCnN5bmVyZ3kuc2V0Lm1vZGVscy5udW0gPSBjb3VudF9tb2RlbHNfdGhhdF9wcmVkaWN0X3N5bmVyZ2llcyhzeW5lcmd5LnNldC5zdHIsIG1vZGVsLnByZWRpY3Rpb25zKQpzeW5lcmd5LnN1YnNldC5tb2RlbHMubnVtID0gY291bnRfbW9kZWxzX3RoYXRfcHJlZGljdF9zeW5lcmdpZXMoc3luZXJneS5zdWJzZXQuc3RyLCBtb2RlbC5wcmVkaWN0aW9ucykKCnBsb3RfYXZnX3N0YXRlX2RpZmZfZ3JhcGgobmV0LCBkaWZmID0gZGlmZi5tYXRbIkFLLVBELEJJLVBELFBELVBJIHZzIEJJLVBELFBELVBJIiwgXSwgbGF5b3V0ID0gbmljZS5sYXlvdXQsIAogIHRpdGxlID0gcGFzdGUwKCJTZXQ6ICIsIHN5bmVyZ3kuc2V0LCAiICgiLCBzeW5lcmd5LnNldC5tb2RlbHMubnVtLCAiIG1vZGVscykgdnMgU3Vic2V0OiAiLCBzeW5lcmd5LnN1YnNldCwgIiAoIiwgc3luZXJneS5zdWJzZXQubW9kZWxzLm51bSwgIiBtb2RlbHMpIikpCmBgYAoKPGRpdiBjbGFzcz0iYmx1ZS1ib3giPgpXZSBjb25jbHVkZSB0aGF0IGluIG9yZGVyIGZvciB0aGUgYEFLLVBEYCBkcnVnIGNvbWJpbmF0aW9uIHRvIGJlIHN5bmVyZ2lzdGljLCB0aGUgY2FuY2VyIG1vZGVsIGhhcyB0byBoYXZlIHRoZSBub2RlcyBgU1JDYCBhbmQgYFBUUE42YCBpbiBhbiAqKmluaGliaXRlZCBzdGF0ZSoqLgo8L2Rpdj4KCioqUGFwZXJzIHN1cHBvcnRpbmcgdGhlIGFib3ZlPyoqCgojIyMgUmFuZG9tIHstfQoKV2UgZ2V0IHRoZSBhdmVyYWdlIHN0YXRlIGFuZCBsaW5rIG9wZXJhdG9yIGRpZmZlcmVuY2VzIHBlciBuZXR3b3JrIG5vZGUgZm9yIHRoZSBgQTQ5OGAgY2VsbCBsaW5lIGZyb20gdGhlIHJhbmRvbSBtb2RlbHM6CgpgYGB7ciBBSy1QRCBkaWZmIChSYW5kb20gbW9kZWxzIC0gQTQ5OCl9CkFLLlBELmF2Zy5zdGF0ZS5kaWZmLnJhbmRvbSA9IHJhbmRvbS5zeW5lcmd5LmFuYWx5c2lzLnJlcyRBNDk4JGRpZmYuc3RhdGUuc3luZXJnaWVzLm1hdFsiQUstUEQiLCBdCkFLLlBELmF2Zy5saW5rLmRpZmYucmFuZG9tICA9IHJhbmRvbS5zeW5lcmd5LmFuYWx5c2lzLnJlcyRBNDk4JGRpZmYubGluay5zeW5lcmdpZXMubWF0WyJBSy1QRCIsIF0KYGBgCgpXZSB3aWxsIG5vdyB2aXN1YWxpemUgdGhlIG5vZGVzIGF2ZXJhZ2Ugc3RhdGUgZGlmZmVyZW5jZXMgaW4gYSBuZXR3b3JrIGdyYXBoLiBOb3RlIHRoYXQgdGhlICoqZ29vZCBtb2RlbHMqKiBhcmUgdGhvc2UgdGhhdCBwcmVkaWN0ZWQgdGhlIGBBSy1QRGAgZHJ1ZyBjb21iaW5hdGlvbiB0byBiZSAqc3luZXJnaXN0aWMqIGFuZCB3ZXJlIGNvbnRyYXN0ZWQgdG8gdGhvc2UgdGhhdCBwcmVkaWN0ZWQgaXQgdG8gYmUgKmFudGFnb25pc3RpYyogKCoqYmFkIG1vZGVscyoqKS4gVGhlIG51bWJlciBvZiBtb2RlbHMgaW4gZWFjaCBjYXRlZ29yeSB3ZXJlOgoKYGBge3IgQ291bnQgZ29vZCB2cyBiYWQgbW9kZWxzIGZvciBBSy1QRCBzeW5lcmd5IChyYW5kb20gbW9kZWxzKSwgcmVzdWx0cz0nYXNpcyd9CmRydWcuY29tYiA9ICJBSy1QRCIKCmdvb2QubW9kZWxzLm51bSA9IHN1bShyYW5kb20ubW9kZWwucHJlZGljdGlvbnNbLCBkcnVnLmNvbWJdID09IDEgJiAhaXMubmEocmFuZG9tLm1vZGVsLnByZWRpY3Rpb25zWywgZHJ1Zy5jb21iXSkpCmJhZC5tb2RlbHMubnVtICA9IHN1bShyYW5kb20ubW9kZWwucHJlZGljdGlvbnNbLCBkcnVnLmNvbWJdID09IDAgJiAhaXMubmEocmFuZG9tLm1vZGVsLnByZWRpY3Rpb25zWywgZHJ1Zy5jb21iXSkpCgpwcmV0dHlfcHJpbnRfc3RyaW5nKHBhc3RlMCgiTnVtYmVyIG9mIHJhbmRvbSBtb2RlbHMgKEFLLVBEIHN5bmVyZ2lzdGljKSBpbiB0aGUgQTQ5OCBjZWxsIGxpbmU6ICIsIGdvb2QubW9kZWxzLm51bSkpCnByZXR0eV9wcmludF9zdHJpbmcocGFzdGUwKCJOdW1iZXIgb2YgcmFuZG9tIG1vZGVscyAoQUstUEQgYW50YWdvbmlzdGljKSBpbiB0aGUgQTQ5OCBjZWxsIGxpbmU6ICIsIGJhZC5tb2RlbHMubnVtKSkKYGBgCgpgYGB7ciBBSy1QRCBhY3Rpdml0eSBzdGF0ZSBiaW9tYXJrZXJzIChSYW5kb20gbW9kZWxzIC0gQTQ5OCksIGNhY2hlID0gVFJVRX0KcGxvdF9hdmdfc3RhdGVfZGlmZl9ncmFwaChuZXQsIGRpZmYgPSBBSy5QRC5hdmcuc3RhdGUuZGlmZi5yYW5kb20sIAogIGxheW91dCA9IG5pY2UubGF5b3V0LCB0aXRsZSA9ICJBSy1QRCBhY3Rpdml0eSBzdGF0ZSBiaW9tYXJrZXJzIChSYW5kb20gbW9kZWxzIC0gQTQ5OCkiKQpgYGAKClRodXMsIHdlIGNhbiBpZGVudGlmeSB0aGUgYWN0aXZlIHN0YXRlIGJpb21hcmtlcnM6CgpgYGB7ciBBSy1QRCBhY3RpdmUgc3RhdGUgYmlvbWFya2VycyAoUmFuZG9tIG1vZGVscyAtIEE0OTgpLCByZXN1bHRzID0gJ2FzaXMnfQpBSy5QRC5hY3RpdmUuYmlvbWFya2VycyA9IEFLLlBELmF2Zy5zdGF0ZS5kaWZmLnJhbmRvbVtBSy5QRC5hdmcuc3RhdGUuZGlmZi5yYW5kb20gPiAwLjddCnByZXR0eV9wcmludF92ZWN0b3JfbmFtZXMoQUsuUEQuYWN0aXZlLmJpb21hcmtlcnMpCmBgYAoKVGhlcmUgYXJlIG5vIGluaGliaXRlZCBzdGF0ZSBiaW9tYXJrZXJzIGF0IHRoZSBzcGVjaWZpZWQgdGhyZXNob2xkIGRpZmZlcmVuY2UgbGV2ZWw6CgpgYGB7ciBBSy1QRCBpbmhpYml0ZWQgc3RhdGUgYmlvbWFya2VycyAoUmFuZG9tIG1vZGVscyAtIEE0OTgpLCByZXN1bHRzID0gJ2FzaXMnfQpBSy5QRC5pbmhpYml0ZWQuYmlvbWFya2VycyA9IEFLLlBELmF2Zy5zdGF0ZS5kaWZmLnJhbmRvbVtBSy5QRC5hdmcuc3RhdGUuZGlmZi5yYW5kb20gPCAtMC43XQpwcmV0dHlfcHJpbnRfdmVjdG9yX25hbWVzKEFLLlBELmluaGliaXRlZC5iaW9tYXJrZXJzKQpgYGAKCjxkaXYgY2xhc3M9ImJsdWUtYm94Ij4KU28sIGFnYWluIHdlIG9ic2VydmUgdGhlIG92ZXJleHByZXNzaW9uIG9mIGBFUktfZmAgaW4gdGhlIG1vZGVscyB0aGF0IHByZWRpY3RlZCB0aGUgYEFLLVBEYCBzeW5lcmd5Lgo8L2Rpdj4KCldlIGFsc28gdmlzdWFsaXplIHRoZSBub2RlcyBhdmVyYWdlIGxpbmsgb3BlcmF0b3IgZGlmZmVyZW5jZXMgaW4gYSBuZXR3b3JrIGdyYXBoOgoKYGBge3IgQUstUEQgbGluayBvcGVyYXRvciBiaW9tYXJrZXJzIChSYW5kb20gbW9kZWxzIC0gQTQ5OCksIGNhY2hlID0gVFJVRX0KcGxvdF9hdmdfbGlua19vcGVyYXRvcl9kaWZmX2dyYXBoKG5ldCwgZGlmZiA9IEFLLlBELmF2Zy5saW5rLmRpZmYucmFuZG9tLCAKICBsYXlvdXQgPSBuaWNlLmxheW91dCwgdGl0bGUgPSAiQUstUEQgbGluayBvcGVyYXRvciBiaW9tYXJrZXJzIChSYW5kb20gbW9kZWxzIC0gQTQ5OCkiKQpgYGAKClRoZSBpbXBvcnRhbmNlIG9mIHRoZSAqKk9SIE5PVCoqIGxpbmsgb3BlcmF0b3IgaW4gdGhlIGJvb2xlYW4gZXF1YXRpb24gb2YgYEVSS19mYCBpcyBhZ2FpbiBwcm92ZW4gdG8gYmUgY3J1Y2lhbCBmb3IgdGhlIG1hbmlmZXN0YXRpb24gb2YgdGhlIGBBSy1QRGAgc3luZXJneSwgYWxvbmcgd2l0aCB0aGUgbGluayBvcGVyYXRvcnMgb2YgdGhlIGVxdWF0aW9ucyBvZiB0aGUgYE1FS19mYCwgYFBURU5gIGFuZCBgUERQSzFgIG5vZGVzLgoKCg==